/*
 ============================================================================
 Name        : matrix_mult.c
 Author      : 
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>

#define MAX_SIZE 10 //Max Matrix Row/Column Size

int MatrixA[MAX_SIZE][MAX_SIZE]; //initial memory allocation for matrix A
int MatrixB[MAX_SIZE][MAX_SIZE]; //initial memory allocation for matrix B
int MatrixC[MAX_SIZE][MAX_SIZE]; //initial memory allocation for matrix C

int RowSizeA; //Row Size of Matrix A
int ColSizeA; //Col Size of Matrix A
int RowSizeB; //Row Size of Matrix B
int ColSizeB; //Col Size of Matrix B

int rowA;  //points current row index for Matrix A
int colB;  //points current column index for Matrix B

pthread_mutex_t lockColRow; //mutex variable

//Creates a Matrix with random integers
void CreateMatrix(int matrix[MAX_SIZE][MAX_SIZE], int RowSize, int ColSize) {

	int i,j;

	for(i=0;i<RowSize;i++)
		for(j=0;j<ColSize;j++) {
			matrix[i][j]=rand() % 10;
		}
}

//Thread functions assigns tasks to threads and
// computes a product of indices according to it
void *ComputeTask(void *nullarg) {
	int i;
	int ThRowA; //selected Row index from Matrix A for current thread
	int ThColB; //Selected Column index from Matrix B for current thread
	int ResC=0; //this is created for each threads stack, therefore it's safe, also vars above.

   pthread_mutex_lock(&lockColRow); //begin critical section
	ThRowA = rowA;		//Assign current row index of Matrix A to the thread
	ThColB = colB;		//Assign current col index of Matrix B to the thread
	colB++;	  //increase column index of Matrix B for following thread that will gain access to enter critical section
	if(colB==ColSizeB) {  //if current column index is last column,
		colB=0; //start from column zero
		rowA++; //increase row index of A

		if(rowA==RowSizeA) { //if row index of A points to last row
		rowA = 0;		//set row index to 0 (actually we finish calculating matrix product)
		}
	}
   pthread_mutex_unlock(&lockColRow); //end critical section

	for(i=0;i<ColSizeA;i++) {  //calculate product of selected Selected Row & Column index
							   //again ResC, i, ThrowA is local to every stack
		ResC += MatrixA[ThRowA][i] * MatrixB[i][ThColB];
	}

	//Write result to resulting matrix C, does not require mutex since
	//none of the threads are writing to the same index
	MatrixC[ThRowA][ThColB] = ResC;

	return(NULL);
}

//Prints a Matrix
void PrintMatrix(int matrix[MAX_SIZE][MAX_SIZE], int RowSize, int ColSize) {

	int i,j;
	for(i=0;i<RowSize;i++) {
		for(j=0;j<ColSize;j++) {
			if(j<ColSize) printf(" %d\t",matrix[i][j]);
		}
		printf("\n");
	}
}

int main(void) {

	int i,j;
	pthread_t thr_matrix[MAX_SIZE][MAX_SIZE];

	printf("\n Matrix A: M x K,  Matrix B: K x N");
	printf("\n Max supported matrix size is %d x %d", (int) MAX_SIZE,(int) MAX_SIZE);
	printf("\n---------------------------------------\n");
	printf("\n Enter row size (M) of Matrix A    : ");
	scanf("%d",&RowSizeA);
	printf("\n Enter column size (K) of Matrix A : ");
	scanf("%d",&ColSizeA);
	printf("\n Enter column size (N) of Matrix B : ");
	scanf("%d",&ColSizeB);

	RowSizeB = ColSizeA;
	rowA = 0; //initialize row index for Matrix A
	colB = 0; //initialize column index for Matrix B

	pthread_mutex_init(&lockColRow,NULL);

	srand((unsigned)time(NULL));
	CreateMatrix(MatrixA,RowSizeA,ColSizeA);

	srand((unsigned)time(NULL)+ 2);
	CreateMatrix(MatrixB,RowSizeB,ColSizeB );


for(i=0;i<RowSizeA;i++)
	for(j=0;j<ColSizeB;j++) {
		if( pthread_create(&thr_matrix[i][j],NULL,ComputeTask,NULL) != 0) {
			perror("phread_create");
			exit(1);
		}
	}

for(i=0;i<RowSizeA;i++)
	for(j=0;j<ColSizeB;j++) {
		if( pthread_join(thr_matrix[i][j],NULL) != 0){
			perror("pthread_join");
			exit(1);
		}
	}

	printf("\nMatrix A\n---------\n");
	PrintMatrix(MatrixA,RowSizeA,ColSizeA);

	printf("\nMatrix B\n---------\n");
	PrintMatrix(MatrixB,RowSizeB,ColSizeB);

	printf("\nMatrix A x B\n-------------\n");
	PrintMatrix(MatrixC,RowSizeA,ColSizeB);

	printf("\n");
	return 0;
}
